<!doctype html>
<html lang="zh-CN">
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>NotifyCenter Admin</title>
    <script src="https://cdn.tailwindcss.com"></script>
</head>
<body class="bg-slate-50 text-slate-900 font-sans min-h-screen">
    <div id="app" class="min-h-screen flex flex-col">
        <header class="bg-blue-600 text-white px-6 py-6 shadow">
            <div class="max-w-6xl mx-auto flex flex-col gap-2 md:flex-row md:items-center md:justify-between">
                <div>
                    <h1 class="text-2xl font-semibold">NotifyCenter 管理</h1>
                    <p class="text-blue-100 text-sm">Project 维护面板</p>
                </div>
                <div class="flex gap-3">
                    <button class="rounded-md bg-white/20 px-4 py-2 text-sm font-semibold backdrop-blur"
                            @click="openCreateModal">新建项目</button>
                    <button class="rounded-md bg-white/20 px-4 py-2 text-sm font-semibold backdrop-blur"
                            @click="loadProjects" :disabled="loading">刷新</button>
                </div>
            </div>
        </header>

        <main class="max-w-6xl mx-auto w-full px-4 py-6 space-y-5 flex-1">
            <section v-if="errorMessage" class="rounded-md bg-rose-100 text-rose-800 px-4 py-3 text-sm font-semibold shadow">{{ errorMessage }}</section>
            <section v-if="successMessage" class="rounded-md bg-emerald-100 text-emerald-800 px-4 py-3 text-sm font-semibold shadow">{{ successMessage }}</section>

            <section class="grid gap-4 md:grid-cols-2">
                <div class="bg-white rounded-lg shadow p-5 flex items-center justify-between">
                    <div>
                        <p class="text-xs uppercase tracking-wide text-slate-500">项目数量</p>
                        <p class="text-3xl font-semibold text-slate-900 mt-1">{{ page.totalCount }}</p>
                    </div>
                    <div class="text-right">
                        <p class="text-xs uppercase tracking-wide text-slate-500">当前页</p>
                        <p class="text-3xl font-semibold text-slate-900 mt-1">{{ currentPage }}</p>
                    </div>
                </div>
                <div class="bg-white rounded-lg shadow p-5">
                    <p class="text-xs uppercase tracking-wide text-slate-500 mb-3">快捷操作</p>
                    <div class="flex flex-wrap gap-3">
                        <button class="rounded-md bg-blue-600 px-4 py-2 text-white text-sm font-semibold shadow disabled:opacity-60"
                                :disabled="loading" @click="loadProjects">刷新表格</button>
                        <button class="rounded-md bg-slate-200 px-4 py-2 text-sm font-semibold text-slate-700"
                                :disabled="loading" @click="resetFilters">重置筛选</button>
                    </div>
                </div>
            </section>

            <section class="bg-white rounded-lg shadow p-5 space-y-2">
                <div class="flex items-center justify-between">
                    <h3 class="text-base font-semibold text-slate-800">筛选条件</h3>
                    <button class="text-sm text-blue-600" @click="filtersVisible = !filtersVisible">
                        {{ filtersVisible ? '收起' : '展开' }}
                    </button>
                </div>
                <div v-show="filtersVisible" class="grid gap-4 md:grid-cols-2 lg:grid-cols-4">
                    <label class="text-sm font-semibold text-slate-600 space-y-2">
                        <span>名称</span>
                        <input v-model.trim="filters.name" placeholder="模糊匹配"
                               class="w-full rounded-md border border-slate-200 px-3 py-2 text-sm focus:border-blue-500 focus:ring focus:ring-blue-200">
                    </label>
                    <label class="text-sm font-semibold text-slate-600 space-y-2">
                        <span>描述</span>
                        <input v-model.trim="filters.description" placeholder="模糊匹配"
                               class="w-full rounded-md border border-slate-200 px-3 py-2 text-sm focus:border-blue-500 focus:ring focus:ring-blue-200">
                    </label>
                    <label class="text-sm font-semibold text-slate-600 space-y-2">
                        <span>状态</span>
                        <select v-model="filters.enabled"
                                class="w-full rounded-md border border-slate-200 px-3 py-2 text-sm focus:border-blue-500 focus:ring focus:ring-blue-200">
                            <option value="all">全部</option>
                            <option value="true">仅启用</option>
                            <option value="false">仅禁用</option>
                        </select>
                    </label>
                    <label class="text-sm font-semibold text-slate-600 space-y-2">
                        <span>每页数量</span>
                        <input type="number" min="1" max="100" v-model.number="filters.maxResultCount"
                               class="w-full rounded-md border border-slate-200 px-3 py-2 text-sm focus:border-blue-500 focus:ring focus:ring-blue-200">
                    </label>
                </div>
                <div class="flex flex-wrap gap-3 pt-2">
                    <button class="inline-flex items-center rounded-md bg-blue-600 px-4 py-2 text-white text-sm font-semibold shadow disabled:opacity-60"
                            :disabled="loading" @click="loadProjects">查询</button>
                    <button class="inline-flex items-center rounded-md bg-amber-500 px-4 py-2 text-white text-sm font-semibold shadow disabled:opacity-60"
                            :disabled="loading" @click="resetFilters">重置</button>
                </div>
            </section>

            <section class="bg-white rounded-lg shadow p-5 space-y-2">
                <header class="flex flex-wrap items-center justify-between gap-3">
                    <div>
                        <h3 class="text-base font-semibold text-slate-800">项目列表</h3>
                        <p class="text-xs text-slate-500">共 {{ page.totalCount }} 条数据</p>
                    </div>
                    <div class="flex items-center gap-3 text-sm">
                        <button class="rounded-md border border-slate-200 px-3 py-1.5 disabled:opacity-50"
                                :disabled="loading || filters.skipCount <= 0" @click="goPrev">上一页</button>
                        <span class="text-slate-500">{{ currentPage }} / {{ totalPages }}</span>
                        <button class="rounded-md border border-slate-200 px-3 py-1.5 disabled:opacity-50"
                                :disabled="loading || !hasMore" @click="goNext">下一页</button>
                    </div>
                </header>

                <div v-if="loading" class="text-slate-500 text-sm">正在加载...</div>
                <div v-else-if="projects.length === 0" class="text-slate-500 text-sm">暂无项目，试试调整筛选条件。</div>
                <div v-else class="overflow-x-auto">
                    <table class="min-w-full divide-y divide-slate-100 text-sm">
                        <thead class="bg-slate-50 text-xs uppercase tracking-wide text-slate-500">
                            <tr>
                                <th class="px-3 py-2 text-left font-semibold">ID</th>
                                <th class="px-3 py-2 text-left font-semibold">名称</th>
                                <th class="px-3 py-2 text-left font-semibold">描述</th>
                                <th class="px-3 py-2 text-left font-semibold">状态</th>
                                <th class="px-3 py-2 text-left font-semibold">操作</th>
                            </tr>
                        </thead>
                        <tbody class="divide-y divide-slate-100">
                            <tr v-for="project in projects" :key="project.id" class="hover:bg-slate-50">
                                <td class="px-3 py-2">
                                    <div class="flex items-center gap-2">
                                        <span class="font-mono text-xs">{{ project.id }}</span>
                                        <button class="text-[10px] px-2 py-0.5 rounded border border-slate-300 text-slate-600 hover:bg-slate-100"
                                                @click="copyToClipboard(project.id, 'ProjectId')">
                                            复制
                                        </button>
                                    </div>
                                </td>
                                <td class="px-3 py-2 font-semibold text-slate-800 truncate max-w-xs">{{ project.name }}</td>
                                <td class="px-3 py-2 text-slate-600 truncate max-w-md">{{ project.description }}</td>
                                <td class="px-3 py-2">
                                    <span class="inline-flex rounded-full px-3 py-1 text-xs font-semibold"
                                          :class="project.enabled ? 'bg-emerald-100 text-emerald-700' : 'bg-rose-100 text-rose-700'">
                                        {{ project.enabled ? '启用' : '禁用' }}
                                    </span>
                                </td>
                                <td class="px-3 py-2">
                                    <div class="flex flex-wrap gap-2">
                                        <button class="rounded-lg transition duration-300 hover:border-gray-400 border border-slate-200 px-3 py-1.5 text-xs font-medium"
                                                @click="openEditModal(project)">编辑</button>
                                        <button class="rounded-lg transition duration-300 hover:border-gray-400 border border-slate-200 px-3 py-1.5 text-xs font-medium"
                                                @click="openNotificationModal(project)">通知配置</button>
                                        <button class="rounded-lg transition duration-300 hover:border-gray-400 px-3 py-1.5 text-xs font-medium text-white"
                                                :class="project.enabled ? 'bg-amber-500' : 'bg-emerald-600'"
                                                :disabled="saving" @click="toggleProject(project)">
                                            {{ project.enabled ? '禁用' : '启用' }}
                                        </button>
                                    </div>
                                </td>
                            </tr>
                        </tbody>
                    </table>
                </div>
            </section>
        </main>

        <div v-if="showCreateModal" class="fixed inset-0 z-50 flex items-center justify-center bg-black/40 backdrop-blur">
        <div class="bg-white rounded-lg shadow-2xl max-w-lg w-full mx-4 p-6 space-y-2">
            <div class="flex items-center justify-between">
                <h3 class="text-lg font-semibold text-slate-800">新建项目</h3>
                <button class="text-slate-400 hover:text-slate-600" @click="closeCreateModal">×</button>
            </div>
            <div class="space-y-3">
                <label class="text-sm font-semibold text-slate-600 space-y-2">
                    <span>名称</span>
                    <input v-model.trim="createModel.name" placeholder="名称"
                           class="w-full rounded-md border border-slate-200 px-3 py-2 text-sm focus:border-blue-500 focus:ring focus:ring-blue-200">
                </label>
                <label class="text-sm font-semibold text-slate-600 space-y-2">
                    <span>描述</span>
                    <textarea v-model.trim="createModel.description" placeholder="描述"
                              class="w-full rounded-md border border-slate-200 px-3 py-2 text-sm min-h-[90px] focus:border-blue-500 focus:ring focus:ring-blue-200"></textarea>
                </label>
            </div>
            <div class="flex justify-end gap-3">
                <button class="rounded-md bg-slate-200 px-4 py-2 text-sm font-semibold text-slate-700" @click="closeCreateModal">取消</button>
                <button class="rounded-md bg-emerald-600 px-4 py-2 text-white text-sm font-semibold disabled:opacity-60"
                        :disabled="saving" @click="createProject">创建</button>
            </div>
        </div>
        </div>

        <div v-if="showEditModal" class="fixed inset-0 z-50 flex items-center justify-center bg-black/40 backdrop-blur">
        <div class="bg-white rounded-lg shadow-2xl max-w-lg w-full mx-4 p-6 space-y-2">
            <div class="flex items-center justify-between">
                <h3 class="text-lg font-semibold text-slate-800">编辑项目 - {{ editModel.name }}</h3>
                <button class="text-slate-400 hover:text-slate-600" @click="closeEditModal">×</button>
            </div>
            <div class="space-y-3">
                <label class="text-sm font-semibold text-slate-600 space-y-2">
                    <span>名称</span>
                    <input v-model.trim="editModel.name"
                           class="w-full rounded-md border border-slate-200 px-3 py-2 text-sm focus:border-blue-500 focus:ring focus:ring-blue-200">
                </label>
                <label class="text-sm font-semibold text-slate-600 space-y-2">
                    <span>描述</span>
                    <textarea v-model.trim="editModel.description"
                              class="w-full rounded-md border border-slate-200 px-3 py-2 text-sm min-h-[90px] focus:border-blue-500 focus:ring focus:ring-blue-200"></textarea>
                </label>
            </div>
            <div class="flex justify-end gap-3">
                <button class="rounded-md bg-slate-200 px-4 py-2 text-sm font-semibold text-slate-700" @click="closeEditModal">取消</button>
                <button class="rounded-md bg-blue-600 px-4 py-2 text-white text-sm font-semibold disabled:opacity-60"
                        :disabled="saving" @click="updateProject">保存</button>
            </div>
        </div>
        </div>

        <div v-if="showNotificationModal" class="fixed inset-0 z-50 flex items-center justify-center bg-black/40 backdrop-blur">
        <div class="bg-white rounded-lg shadow-2xl w-4/5 mx-4 p-6 space-y-2">
            <div class="flex items-center justify-between">
                <div>
                    <h3 class="text-lg font-semibold text-slate-800">通知配置 - {{ notificationProject?.name }}</h3>
                    <div class="text-xs text-slate-500 flex items-center gap-2">
                        <span>项目 ID:
                            <span class="font-mono text-slate-700">{{ notificationProject?.id }}</span>
                        </span>
                        <button class="px-2 py-0.5 rounded border border-slate-300 text-slate-600 hover:bg-slate-100 disabled:opacity-40"
                                :disabled="!notificationProject?.id"
                                @click="copyToClipboard(notificationProject?.id, 'ProjectId')">
                            复制
                        </button>
                    </div>
                </div>
                <button class="text-slate-400 hover:text-slate-600" @click="closeNotificationModal">×</button>
            </div>
            <div class="grid gap-4 lg:grid-cols-3">
                <div class="lg:col-span-2 border border-slate-100 rounded-lg">
                    <div class="flex items-center justify-between px-4 py-2 border-b text-sm">
                        <span>通知列表</span>
                        <span class="text-slate-500">{{ notifications.length }} 条</span>
                    </div>
                    <div class="max-h-80 overflow-y-auto">
                        <table class="min-w-full text-sm">
                            <thead class="bg-slate-50 text-xs uppercase tracking-wide text-slate-500">
                                <tr>
                                    <th class="px-3 py-2 text-left font-semibold">名称</th>
                                    <th class="px-3 py-2 text-left font-semibold">类型</th>
                                    <th class="px-3 py-2 text-left font-semibold">Webhook</th>
                                    <th class="px-3 py-2 text-left font-semibold">操作</th>
                                </tr>
                            </thead>
                            <tbody class="divide-y divide-slate-100">
                                <tr v-if="notificationLoading">
                                    <td colspan="4" class="px-3 py-4 text-center text-slate-500">加载中...</td>
                                </tr>
                                <tr v-else-if="notifications.length === 0">
                                    <td colspan="4" class="px-3 py-4 text-center text-slate-400 text-sm">暂无通知配置</td>
                                </tr>
                                <tr v-else v-for="rule in notifications" :key="rule.id" class="hover:bg-slate-50">
                                    <td class="px-3 py-2">
                                        <p class="font-semibold text-slate-800">{{ rule.name }}</p>
                                        <p class="text-xs text-slate-500 truncate">{{ rule.description }}</p>
                                        <div class="mt-1 flex items-center gap-2 text-[11px] text-slate-500">
                                            <span class="font-mono truncate max-w-[140px]">ID: {{ rule.id }}</span>
                                            <button class="px-2 py-0.5 rounded border border-slate-300 text-slate-600 hover:bg-slate-100"
                                                    @click="copyToClipboard(rule.id, 'NotificationId')">
                                                复制
                                            </button>
                                        </div>
                                    </td>
                                    <td class="px-3 py-2">
                                        <span class="rounded-full bg-slate-100 px-3 py-1 text-xs font-medium text-slate-600">{{ rule.type }}</span>
                                    </td>
                                    <td class="px-3 py-2">
                                        <span class="text-xs" :class="rule.enableWebhook ? 'text-emerald-600' : 'text-slate-400'">
                                            {{ rule.enableWebhook ? (rule.webhookUrl || '已开启') : '未启用' }}
                                        </span>
                                    </td>
                                    <td class="px-3 py-2">
                                        <button class="rounded-lg border border-slate-200 px-3 py-1 text-xs font-medium"
                                                @click="editNotification(rule)">编辑</button>
                                    </td>
                                </tr>
                            </tbody>
                        </table>
                    </div>
                </div>
                <div class="border border-slate-100 rounded-lg p-4 space-y-3">
                    <div class="flex items-center justify-between">
                        <h4 class="text-sm font-semibold text-slate-800">
                            {{ editingNotificationId ? '编辑通知' : '新增通知' }}
                        </h4>
                        <button class="text-xs text-blue-600" @click="resetNotificationForm" v-if="editingNotificationId">新建</button>
                    </div>
                    <div v-if="editingNotificationId" class="text-[11px] text-slate-500 flex items-center gap-2">
                        <span>通知 ID:
                            <span class="font-mono text-slate-700">{{ editingNotificationId }}</span>
                        </span>
                        <button class="px-2 py-0.5 rounded border border-slate-300 text-slate-600 hover:bg-slate-100"
                                @click="copyToClipboard(editingNotificationId, 'NotificationId')">
                            复制
                        </button>
                    </div>
                    <div class="space-y-3 text-sm">
                        <label class="text-xs font-semibold text-slate-600 space-y-1">
                            <span>名称</span>
                            <input v-model.trim="notificationForm.name" class="w-full rounded-md border border-slate-200 px-3 py-2 text-sm">
                        </label>
                        <label class="text-xs font-semibold text-slate-600 space-y-1">
                            <span>描述</span>
                            <textarea v-model.trim="notificationForm.description"
                                      class="w-full rounded-md border border-slate-200 px-3 py-2 text-sm min-h-[70px]"></textarea>
                        </label>
                        <label class="text-xs font-semibold text-slate-600 space-y-1">
                            <span>类型标识</span>
                            <select v-model="notificationForm.type"
                                    :disabled="senderTypesLoading || !senderTypes.length"
                                    class="w-full rounded-md border border-slate-200 px-3 py-2 text-sm bg-white">
                                <option value="" disabled>请选择通知类型</option>
                                <option v-for="senderType in senderTypes" :key="senderType" :value="senderType">
                                    {{ senderType }}
                                </option>
                            </select>
                            <p v-if="senderTypesLoading" class="text-[11px] text-slate-500">通知类型加载中...</p>
                            <p v-else-if="!senderTypes.length" class="text-[11px] text-amber-600">暂无可用通知类型，请检查后台配置。</p>
                        </label>
                        <label class="text-xs font-semibold text-slate-600 space-y-1">
                            <span>配置 JSON</span>
                            <textarea v-model="notificationForm.configurationJson"
                                      class="w-full rounded-md border border-slate-200 px-3 py-2 text-xs font-mono min-h-[90px]"></textarea>
                        </label>
                        <label class="inline-flex items-center gap-2 text-xs font-semibold text-slate-600">
                            <input type="checkbox" v-model="notificationForm.enableWebhook" class="rounded border-slate-300">
                            启用 Webhook
                        </label>
                        <label class="text-xs font-semibold text-slate-600 space-y-1" :class="{ 'opacity-50': !notificationForm.enableWebhook }">
                            <span>Webhook URL</span>
                            <input v-model.trim="notificationForm.webhookUrl" :disabled="!notificationForm.enableWebhook"
                                   class="w-full rounded-md border border-slate-200 px-3 py-2 text-sm">
                        </label>
                        <label class="text-xs font-semibold text-slate-600 space-y-1" :class="{ 'opacity-50': !notificationForm.enableWebhook }">
                            <span>Webhook Secret</span>
                            <input v-model.trim="notificationForm.webhookSecret" :disabled="!notificationForm.enableWebhook"
                                   class="w-full rounded-md border border-slate-200 px-3 py-2 text-sm">
                        </label>
                        <label class="text-xs font-semibold text-slate-600 space-y-1">
                            <span>最大重试次数 (1-10)</span>
                            <input type="number" min="1" max="10" v-model.number="notificationForm.maxRetryTimes"
                                   class="w-full rounded-md border border-slate-200 px-3 py-2 text-sm">
                        </label>
                    </div>
                    <div class="flex justify-end gap-2 pt-2">
                        <button class="rounded-md bg-slate-200 px-4 py-2 text-xs font-semibold text-slate-700"
                                @click="closeNotificationModal">关闭</button>
                        <button class="rounded-md bg-emerald-600 px-4 py-2 text-xs font-semibold text-white disabled:opacity-60"
                                :disabled="notificationSaving" @click="saveNotification">
                            {{ editingNotificationId ? '保存修改' : '新增通知' }}
                        </button>
                    </div>
                </div>
            </div>
        </div>
        </div>
    </div>

    <script src="https://unpkg.com/vue@3/dist/vue.global.prod.js"></script>
    <script>
        const { createApp } = Vue;
        const apiBase = "/project";
        const defaultNotificationForm = () => ({
            name: "",
            description: "",
            type: "",
            configurationJson: "{}",
            enableWebhook: false,
            webhookUrl: "",
            webhookSecret: "",
            maxRetryTimes: null
        });

        createApp({
            data() {
                return {
                    loading: false,
                    saving: false,
                    filtersVisible: true,
                    projects: [],
                    page: { totalCount: 0, items: [] },
                    filters: {
                        name: "",
                        description: "",
                        enabled: "all",
                        maxResultCount: 20,
                        skipCount: 0
                    },
                    createModel: {
                        name: "",
                        description: ""
                    },
                    showCreateModal: false,
                    showEditModal: false,
                    showNotificationModal: false,
                    editingProject: null,
                    editModel: {
                        name: "",
                        description: ""
                    },
                    notificationProject: null,
                    notifications: [],
                    notificationLoading: false,
                    notificationSaving: false,
                    editingNotificationId: null,
                    notificationForm: defaultNotificationForm(),
                    senderTypes: [],
                    senderTypesLoading: false,
                    errorMessage: "",
                    successMessage: ""
                };
            },
            computed: {
                hasMore() {
                    return (this.filters.skipCount + this.filters.maxResultCount) < this.page.totalCount;
                },
                currentPage() {
                    return Math.floor(Math.max(0, this.filters.skipCount) / Math.max(1, this.filters.maxResultCount)) + 1;
                },
                totalPages() {
                    if (this.page.totalCount === 0) return 1;
                    return Math.ceil(this.page.totalCount / Math.max(1, this.filters.maxResultCount));
                }
            },
            mounted() {
                this.loadProjects();
                this.loadSenderTypes();
            },
            methods: {
                buildQuery() {
                    const params = new URLSearchParams();
                    params.set("SkipCount", this.filters.skipCount);
                    params.set("MaxResultCount", this.filters.maxResultCount);
                    if (this.filters.name) params.set("Name", this.filters.name);
                    if (this.filters.description) params.set("Description", this.filters.description);
                    if (this.filters.enabled === "true") params.set("Enabled", true);
                    if (this.filters.enabled === "false") params.set("Enabled", false);
                    return params.toString();
                },
                async copyToClipboard(value, label = "内容") {
                    if (!value && value !== 0) return;
                    const text = String(value);
                    const successMsg = `${label} 已复制`;
                    try {
                        if (navigator && navigator.clipboard && navigator.clipboard.writeText) {
                            await navigator.clipboard.writeText(text);
                        } else {
                            const textarea = document.createElement("textarea");
                            textarea.value = text;
                            textarea.style.position = "fixed";
                            textarea.style.opacity = "0";
                            document.body.appendChild(textarea);
                            textarea.focus();
                            textarea.select();
                            document.execCommand("copy");
                            document.body.removeChild(textarea);
                        }
                        this.successMessage = successMsg;
                        setTimeout(() => {
                            if (this.successMessage === successMsg) {
                                this.successMessage = "";
                            }
                        }, 2000);
                    } catch (err) {
                        console.error(err);
                        this.errorMessage = "复制失败，请手动复制";
                    }
                },
                prefillDefaultSenderType() {
                    if (!this.notificationForm.type && this.senderTypes.length) {
                        this.notificationForm.type = this.senderTypes[0];
                    }
                },
                async loadSenderTypes() {
                    if (this.senderTypesLoading) return;
                    this.senderTypesLoading = true;
                    try {
                        const res = await fetch(`${apiBase}/sender-types`);
                        if (!res.ok) throw new Error(`通知类型加载失败 (${res.status})`);
                        const raw = await res.text();
                        let parsedTypes = [];
                        if (raw) {
                            try {
                                const parsed = JSON.parse(raw);
                                if (Array.isArray(parsed)) {
                                    parsedTypes = parsed;
                                } else if (parsed && Array.isArray(parsed.items)) {
                                    parsedTypes = parsed.items;
                                } else if (typeof parsed === "string") {
                                    parsedTypes = [parsed];
                                }
                            } catch {
                                parsedTypes = raw
                                    .split(/[\r\n,]+/)
                                    .map(item => item.trim())
                                    .filter(Boolean);
                            }
                        }
                        this.senderTypes = parsedTypes;
                        this.prefillDefaultSenderType();
                    } catch (err) {
                        console.error(err);
                        this.errorMessage = err.message ?? "通知类型加载失败";
                    } finally {
                        this.senderTypesLoading = false;
                    }
                },
                async loadProjects() {
                    this.loading = true;
                    this.errorMessage = "";
                    try {
                        const res = await fetch(`${apiBase}?${this.buildQuery()}`);
                        if (!res.ok) throw new Error(`加载失败 (${res.status})`);
                        const data = await res.json();
                        this.page = data;
                        this.projects = data.items ?? [];
                    } catch (err) {
                        console.error(err);
                        this.errorMessage = err.message ?? "加载失败";
                    } finally {
                        this.loading = false;
                    }
                },
                resetFilters() {
                    this.filters = {
                        name: "",
                        description: "",
                        enabled: "all",
                        maxResultCount: 20,
                        skipCount: 0
                    };
                    this.loadProjects();
                },
                openCreateModal() {
                    this.showCreateModal = true;
                },
                closeCreateModal() {
                    this.showCreateModal = false;
                    this.createModel = { name: "", description: "" };
                },
                async createProject() {
                    if (!this.createModel.name) {
                        this.errorMessage = "项目名称不能为空";
                        return;
                    }
                    this.saving = true;
                    this.successMessage = "";
                    this.errorMessage = "";
                    try {
                        const res = await fetch(apiBase, {
                            method: "POST",
                            headers: { "Content-Type": "application/json" },
                            body: JSON.stringify({
                                name: this.createModel.name,
                                description: this.createModel.description ?? ""
                            })
                        });
                        if (!res.ok) throw new Error(`创建失败 (${res.status})`);
                        this.successMessage = "项目创建成功";
                        this.closeCreateModal();
                        await this.loadProjects();
                    } catch (err) {
                        console.error(err);
                        this.errorMessage = err.message ?? "创建失败";
                    } finally {
                        this.saving = false;
                    }
                },
                openEditModal(project) {
                    this.editingProject = project;
                    this.editModel = {
                        name: project.name,
                        description: project.description
                    };
                    this.showEditModal = true;
                },
                closeEditModal() {
                    this.showEditModal = false;
                    this.editingProject = null;
                    this.editModel = { name: "", description: "" };
                },
                async updateProject() {
                    if (!this.editingProject) return;
                    if (!this.editModel.name) {
                        this.errorMessage = "项目名称不能为空";
                        return;
                    }
                    this.saving = true;
                    this.successMessage = "";
                    this.errorMessage = "";
                    try {
                        const res = await fetch(`${apiBase}/${this.editingProject.id}`, {
                            method: "PUT",
                            headers: { "Content-Type": "application/json" },
                            body: JSON.stringify({
                                name: this.editModel.name,
                                description: this.editModel.description ?? ""
                            })
                        });
                        if (!res.ok) throw new Error(`更新失败 (${res.status})`);
                        this.successMessage = "项目已更新";
                        this.closeEditModal();
                        await this.loadProjects();
                    } catch (err) {
                        console.error(err);
                        this.errorMessage = err.message ?? "更新失败";
                    } finally {
                        this.saving = false;
                    }
                },
                async toggleProject(project) {
                    this.saving = true;
                    this.successMessage = "";
                    this.errorMessage = "";
                    try {
                        const res = await fetch(`${apiBase}/${project.id}/enabled?enable=${!project.enabled}`, {
                            method: "PUT"
                        });
                        if (!res.ok) throw new Error(`切换失败 (${res.status})`);
                        this.successMessage = `项目「${project.name}」已${project.enabled ? "禁用" : "启用"}`;
                        await this.loadProjects();
                    } catch (err) {
                        console.error(err);
                        this.errorMessage = err.message ?? "切换失败";
                    } finally {
                        this.saving = false;
                    }
                },
                async goPrev() {
                    if (this.filters.skipCount <= 0) return;
                    this.filters.skipCount = Math.max(0, this.filters.skipCount - this.filters.maxResultCount);
                    await this.loadProjects();
                },
                async goNext() {
                    if (!this.hasMore) return;
                    this.filters.skipCount += this.filters.maxResultCount;
                    await this.loadProjects();
                },
                openNotificationModal(project) {
                    this.notificationProject = project;
                    this.showNotificationModal = true;
                    this.resetNotificationForm();
                    if (!this.senderTypes.length && !this.senderTypesLoading) {
                        this.loadSenderTypes();
                    }
                    this.loadNotifications();
                },
                async loadNotifications() {
                    if (!this.notificationProject) return;
                    this.notificationLoading = true;
                    try {
                        const res = await fetch(`${apiBase}/${this.notificationProject.id}/notifications`);
                        if (!res.ok) throw new Error(`加载通知失败 (${res.status})`);
                        const data = await res.json();
                        this.notifications = data ?? [];
                    } catch (err) {
                        console.error(err);
                        this.errorMessage = err.message ?? "加载通知失败";
                    } finally {
                        this.notificationLoading = false;
                    }
                },
                editNotification(rule) {
                    this.editingNotificationId = rule.id;
                    this.notificationForm = {
                        name: rule.name,
                        description: rule.description,
                        type: rule.type,
                        configurationJson: rule.configurationJson ?? "{}",
                        enableWebhook: rule.enableWebhook ?? false,
                        webhookUrl: rule.webhookUrl ?? "",
                        webhookSecret: rule.webhookSecret ?? "",
                        maxRetryTimes: rule.maxRetryTimes ?? null
                    };
                },
                resetNotificationForm() {
                    this.editingNotificationId = null;
                    this.notificationForm = defaultNotificationForm();
                    this.prefillDefaultSenderType();
                },
                async saveNotification() {
                    if (!this.notificationProject) return;
                    if (!this.notificationForm.name || !this.notificationForm.type) {
                        this.errorMessage = "通知名称和类型必填";
                        return;
                    }
                    this.notificationSaving = true;
                    this.errorMessage = "";
                    this.successMessage = "";
                    const payload = {
                        name: this.notificationForm.name,
                        description: this.notificationForm.description ?? "",
                        type: this.notificationForm.type,
                        configurationJson: this.notificationForm.configurationJson || "{}",
                        enableWebhook: !!this.notificationForm.enableWebhook,
                        webhookUrl: this.notificationForm.enableWebhook ? (this.notificationForm.webhookUrl ?? "") : null,
                        webhookSecret: this.notificationForm.enableWebhook ? (this.notificationForm.webhookSecret ?? "") : null,
                        maxRetryTimes: this.notificationForm.maxRetryTimes || null
                    };
                    const method = this.editingNotificationId ? "PUT" : "POST";
                    const url = this.editingNotificationId
                        ? `${apiBase}/${this.notificationProject.id}/notifications/${this.editingNotificationId}`
                        : `${apiBase}/${this.notificationProject.id}/notifications`;
                    try {
                        const res = await fetch(url, {
                            method,
                            headers: { "Content-Type": "application/json" },
                            body: JSON.stringify(payload)
                        });
                        if (!res.ok) throw new Error(`保存通知失败 (${res.status})`);
                        this.successMessage = this.editingNotificationId ? "通知已更新" : "通知已创建";
                        this.resetNotificationForm();
                        await this.loadNotifications();
                    } catch (err) {
                        console.error(err);
                        this.errorMessage = err.message ?? "保存通知失败";
                    } finally {
                        this.notificationSaving = false;
                    }
                },
                closeNotificationModal() {
                    this.showNotificationModal = false;
                    this.notificationProject = null;
                    this.notifications = [];
                    this.resetNotificationForm();
                }
            }
        }).mount("#app");
    </script>
</body>
</html>
